///////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////

package navigator
{
	
///////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////

import flash.errors.IllegalOperationError;
import flash.events.Event;
import flash.events.IOErrorEvent;
import flash.events.SecurityErrorEvent;
import flash.utils.getDefinitionByName;

import mx.controls.DataGrid;
import mx.controls.TextInput;
import mx.controls.dataGridClasses.DataGridColumn;
import mx.events.DataGridEvent;
 
import summit.basecamp.wrappers.AbstractWrapper;
import summit.basecamp.wrappers.CommentWrapper;
import summit.basecamp.wrappers.IParentWrapper;
import summit.basecamp.wrappers.IStandardWrapper;
import summit.events.BasecampAPIEvent;
import summit.net.BasecampLoader;

import navigator.ui.PropertiesViewer;

///////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////

/** Helpers for use by application UI code/ **/	
public class Helpers
{
	/** Registers generic loader and API event handlers. 
	 * <p>API Issues (Failure and Error) are seperately from Loader 
	 * errors (IO Error & Security Error).</p> **/
	public static function registerCommonEvents(loader:BasecampLoader):void
	{
		loader.addEventListener(BasecampAPIEvent.ERROR, handleAPIIssue);
		loader.addEventListener(BasecampAPIEvent.FAIL, handleAPIIssue);
		
		loader.addEventListener(IOErrorEvent.IO_ERROR, handleLoaderError);
		loader.addEventListener(SecurityErrorEvent.SECURITY_ERROR, 
		handleLoaderError);
		
		loader.accountToken = State.accountToken;
	}
	
	/** Gets a resource class from a resource attribute string **/
	public static function getClass(resourceStr:String):AbstractWrapper
	{
		if(!resourceStr || "" == resourceStr) return null;
		
		var qualifiedName:String = Constants.WRAPPER_PACKAGE + "::" + 
		resourceStr + "Wrapper";
		
		var ResourceClass:Class = 
		flash.utils.getDefinitionByName(qualifiedName) as Class;
		
		var abstractWrapper:AbstractWrapper = new ResourceClass();
		
		return abstractWrapper;
	}

///////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////
// Helpers for Properties View

	/** @private true if 'Create' should be visible on the property view. **/
	public static function getCreateLabelStatus(
	abstractWrapper:AbstractWrapper, item:XML):Boolean
	{
		if(!item || !item.hasOwnProperty("@apiType")) return false;
		
		if(item.@id == "-2" && abstractWrapper is IStandardWrapper) return true;
		
		if(abstractWrapper is IParentWrapper && 
		(abstractWrapper as IParentWrapper).child is IStandardWrapper) return true;
		
		return false;
	}
	
	/**
	 * @private
	 * 
	 * Sets the labels / buttons based on XML attributes.
	 */
	public static function configureToolBar(
	abstractWrapper:AbstractWrapper):void
	{
		if(abstractWrapper is IStandardWrapper || abstractWrapper is CommentWrapper)
		{
			State.viewer.editLabel.visible = true;
			State.viewer.deleteLabel.visible = true;
		}
		else
		{
			State.viewer.editLabel.visible = false;
			State.viewer.deleteLabel.visible = false;
		}
		
		State.viewer.editLabel.text = "Edit";
		State.viewer.editColumn.editable = false;
		State.viewer.editColumn.setStyle("color", 0x000000);
		State.viewer.createLabel.text = "Create";
	}
	
	
	/**
	 * 
	 * Called to transition DataGrid into 'Edit' XML.
	 * 
	 * Typically called after a template for an item is pulled down from
	 * Basecamp.
	 */
	public static function enterEditMode(xml:XML, isEdit:Boolean = true):void
	{
		delete State.viewer.dataGridXML.item;
		xml.setLocalName("item");
		State.viewer.dataGridXML.item = xml;
		State.viewer.editColumn.editable = true;
		State.viewer.editColumn.setStyle("color", 0x0000FF);
		if(isEdit) State.viewer.editLabel.text = "Submit Edit";
		else State.viewer.createLabel.text = "Submit New Item";
	}
	
	/**
	 * @private
	 * 
	 * Called by the Datagrid after each edit. We need to help the XML edit go 
	 * through correctly.
	 */
	public static function dataGrid_ItemEditEnd(e:DataGridEvent):void
	{
		// need to set the XML by hand as it's not a standard Flex friendly 
		// structure - it's Basecamp structure
		
		var x:XML = ((e.currentTarget as DataGrid).selectedItem as XML);
		x.children()[0] = ((e.currentTarget as DataGrid).itemEditorInstance as 
		TextInput).text;
	}
	
	/**
	 * @private
	 * 
	 * Called for items being added to the first column of the DataGrid.
	 */
	public static function firstColumn_LabelFunction(x:XML, 
	dataGrid:DataGridColumn):String
	{
		return x.localName();
	}
	
	/**
	 * @private
	 * 
	 * Called for items being added to the edit column of the DataGrid.
	 */
	public static function editColumn_LabelFunction(x:XML, 
	dataGrid:DataGridColumn):String
	{
		return x.toString();
	}
	
	/** @private Clear Properties View Tool Bar / Label **/
	public static function resetPropertyView():void
	{
		State.viewer.editLabel.visible = false;
		State.viewer.deleteLabel.visible = false;
		State.viewer.typeLabel.text = PropertiesViewer.SELECTLABEL;
		delete State.viewer.dataGridXML.item;
		State.viewer.editColumn.editable = false;
		State.viewer.editColumn.setStyle("color", 0x000000);
		State.viewer.dataGridXML.appendChild("<item/>");
		State.viewer.createLabel.text = "Create";
	}
///////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////
// Helpers for Tree --- UI and Data toys.
	
	/**
	 * Called to process an OK response from the Basecamp server - to push
	 * data into our treeXML and artifact bucket. This enables the user
	 * to the walk the data as needed.
	 */
	public static function processAPIResult(apiResponse:XML, 
	append:Boolean = true, beforeIndex:int = -1):void
	{
		// Basecamp responses always come like <list><item/><item/></list>
		// We need to walk through each resource and add it to our tree
		// data in a way helps flex display the data to the user.
		for each(var element:XML in apiResponse.children())
		{
			// The entries from basecamp need to be parsed to make subentries
			// in our tree
			var subEntry:XML = new XML("<subEntry/>");
			
			subEntry.@name = (element.hasOwnProperty("name") ? element.name : 
			element.localName());
								  
			subEntry.@id = element.id;
			subEntry.@apiType = element.localName();
			
			// The adapter type is specific to the apiType - in this way - 
			// we'll be able to jump to a specific function
			subEntry = addTypeModifiers(subEntry);
			
			var selectedData:XML = State.tree.selectedItem as XML; 
	
			// For a tiny bit of 'ease-of-use' - we have some additional
			// organization for resources that can pending vs. complete
			selectedData = organizeOnComplete(selectedData, element);
			
			Helpers.insertXMLData(selectedData, subEntry, append, beforeIndex);
			
			// Now that the tree is updated - need to add the XML into the
			// artifact bucket so that it can be viewed in the propertyViewer
			State.resources[element.id] = element;
		}
	}
	
	/** @private Insert with some rules to make updates easier.
	 * 
	 * The normal case simply appends a subEntry at the end of the sourceXML.
	 * However, in other cases - this is a prepend - and in still others - the
	 * new XML needs to be put at a specific index.
	 * 
	 * This is all how to handle adding XML to the tree for list, update and 
	 * edit operations.
	 */
	public static function insertXMLData(sourceXML:XML, subEntry:XML, 
	append:Boolean, beforeIndex:int):void
	{
		if(append) sourceXML.appendChild(subEntry);
		else if(beforeIndex == -1) sourceXML.prependChild(subEntry);
		else 
		{
			if(beforeIndex >= sourceXML.children().length())
			{
				sourceXML.appendChild(subEntry);
			}
			else
			{
				sourceXML.insertChildBefore(
				sourceXML.children()[beforeIndex], subEntry);
			}
		}
	}
	
	/** Determines if a commentable resource has comments.
	 * @return <code>true</code> if the item has comments, <code>false</code>
	 * otherwise. **/
	public static function doesCommentableHaveComments(itemId:String):Boolean
	{ 
		var itemXML:XML = State.resources[itemId];
		
		if(!itemXML.hasOwnProperty("comments-count") || 
		itemXML.child("comments-count")[0].toString() == "0") return false;
		else return true;
	}

	
	/** @private Initially Expands the tree for active projects. */
	public static function expandTree():void
	{
		State.tree.validateNow(); 
		State.tree.expandChildrenOf(((
		State.tree.dataProvider[0]) as XML).children()[0], true);
		
		State.tree.expandItem(State.tree.dataProvider[0], true, true);
	}

	/** @ private Called when building XML nodes for treeXML.
	 * 
	 * Helps process individual items by providing the name of the function
	 * that will forward the call on to summit/basecamp.
	 */
	private static function addTypeModifiers(node:XML):XML
	{
		var apiType:String = node.@apiType;
		
		switch(apiType)
		{
			case "todo-list":
				node.@resource = "TodoList";
			break;
			case "milestones":
			case "milestone":
			   	node.@resource = "Milestone";
			break;
			case "company":
				node.@resource 	= "Company";
			break;
			case "post":
				node.@resource = "Message";
			break;
			case "todo-item":
				node.@resource = "TodoItem";
			break;
			case "comment":
				node.@resource 	= "Comment";
			break;
			case "category":
				node.@resource 	= "Category";
			break;
			case "time-entry":
				node.@resource = "TimeEntry";
			break;
			case "person":
				node.@resource = "Person";
			break;
			default:
				throw new  
				IllegalOperationError("Don't have a case for: " + apiType);
			break;
		}
		
		return node;		
	}
	
	/**
	 * @private
	 * 
	 * Used to add a little more organization to the tree for nodes that 
	 * can be additionally organized with 'pending' and 'completed' groups. 
	 * 
	 * @param selectedData The item currently selected in the XML tree
	 * @param element The current API element being processed
	 * 
	 * @return The XML to be used as the selected item to add the API child
	 */
	private static function organizeOnComplete(selectedData:XML, 
	element:XML):XML
	{
		// Only these elements can be organized based on 'complete'...
		if(	selectedData.@name != "Todo Lists" && 
			selectedData.@apiType != "todo-list" &&
			selectedData.@name != "Milestones") return selectedData;
			
		
		var property:String;
		var sourceXML:XML;
		
		if("true" == element.complete || "true" == element.completed)
		{
			property = "completedItems";
			sourceXML = Constants.todoCompletedXML;
		}
		else
		{
			property = "pendingItems";
			sourceXML = Constants.todoPendingXML;
		}
		
		if(!selectedData.hasOwnProperty(property)) 
		{
			if(property == "completedItems")
				selectedData.appendChild(new XML(sourceXML));
			else selectedData.prependChild(new XML(sourceXML));
		}
		
		selectedData.child(property)[0].@projectId = 
		element.child("project-id").toString();
		
		return selectedData.child(property)[0];
	}
	
///////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////
// Generic Event Handlers

	/** @private Generic handler for API Issues. **/
	private static function handleAPIIssue(e:BasecampAPIEvent):void
	{
		throw new Error("API Issue", -1);
	}
	
	/** @private Generic hadnler for Loader Errors **/
	private static function handleLoaderError(e:Event):void
	{
		throw new Error("Loader Error", -1);
	}
}
}
//////////////////////////////////////////////////////// gmarius@tyemill.com //